#include <stdio.h>

// 操作符的属性：
// 优先级，运算顺序，运算控制（或求值顺序）

// 有问题的代码
// a*b + c*d + e*f
// 优先级只能保证前两个 * 先计算，
// 而第三个 * 不一定比第一个 + 先执行。
// （具体取决于编译器的语法树实现方式）
//
// 如果 a,b,c,d,e,f 为常数，则结果都一样；
// 但如果换成表达式，就可能有潜在的 bug。
//
// 再来：
// c + --c;
// 再来：
// i = i-- - --i * ( i = -3 ) * i++ + ++i;

/*
 * 类型转换（显式、隐式）
 * 显式：在要转换的数据前加上(类型)即可
 *
 * 隐式：精度提升（或整型提升）
 *      操作数的类型不匹配，要先把精度小的向大的转换。
 *      规则：
 *      char, short -> int（自动）
 *      float -> double（自动）
 *
 *      有符号整型 -> 无符号整型（二者同时存在于一条表达式中）
 *      整型 -> 浮点型（二者同时存在于一条表达式中）
 *      long -> long long（二者同时存在于一条表达式中）
 *      double -> long double（二者同时存在于一条表达式中）
 *
 *      其中，对于 char, short -> int -> long long，
 *      精度提升的方式为：符号位向左延长直至满足精度要求。
 *
 *      （特殊：赋值表达式。左值不会被精度提升。）
 */

// int main()
// {
//     char a = 3, b = 127;
//     char c = a + b;
//     printf("%d", c);
//     return 0;
// }
// a 和 b 如何相加？
// 先精度提升送进 CPU 计算，计算结果截取 char 长度赋给 c

// sizeof 里面的运算无效。
// C 标准规定，sizeof 返回类型为 unsigned long long
//

// sizeof 返回类型、精度提升
// int i; // 全局变量不初始化，默认为 0
// int main()
// {
//     --i;
//     if (i > sizeof(i))
//         printf(">\n");
//     else if (i < sizeof(i))
//         printf("<\n");
//     else
//         printf("=\n");
//     return 0;
// }
// 结果令人大跌眼镜：>。
// 原因：int 和 unsigned long long 比较，int 精度提升。

// 成员变量操作符 ., ->

// 函数调用操作符()

// , 从左向右依次执行，以最后一个表达式的结果为最后的结果
// 可用于 while 等

// && 左边为假则右边就不管了，反正结果为假
// || 左边为真则右边就不管了，反正结果为真

/*
 * 位运算符，只能用于整数运算
 * &, |, ^, ~, <<, >>
 *
 * 适当的使用位运算能够提高代码的运行效率，
 * 尤其是在硬件编程中使用较多。
 *
 * &
 * 1.将一个二进制单元清零：&0
 * 2.只保留一个数的指定位。
 *   （其他位设为 0）
 *   只保留低位 2 位：a & ( (1 << 2) - 1 )
 *   只保留高位 4 位：a & ( -1 << (sizeof(a) * 8 - 4) )
 * 3.判断奇偶。
 *   用 if((a&1) == 0) 代替 if((a%2) == 0)
 * 4.判断 2 的整数次方
 *   if( (a&(a-1)) == 0 )
 *
 * |
 * 5.将一个数的某些位设为 1
 *   低位 2 位设为 1：a | ( (1 << 2) - 1 )
 *   高位 4 位设为 1：a | ( -1 << (sizeof(a) * 8 - 4) )
 *
 * ~
 * 6.~a = -(a+1);
 *   （跟负数的原反补码有关）
 * 7.将一个数的某一位设为 0
 *   低位第 3 位设为 0：a & ~(1 << 2)
 *
 * ^
 * 8.翻转一个数的指定位
 *   翻转低位 2 位：a ^ ( (1 << 2) - 1 )
 *   翻转高位 4 位：a ^ ( ((1 << 4) - 1) << (sizeof(a) * 8 - 4) )
 * 9.交换两个整数（见下面）
 * 10.找数组中唯一不成对的数（找出单身狗）
 *
 * <<, >>
 * 11.判断 a 的机器码有没有相邻的 1：
 *    a & (a<<1)
 *
 * 12.TCP/IP 协议中的位运算
 *    计算机能够理解的语言只有二进制，它们之间的通信采用二进制的数据包。
 *
 *    ip 地址：192.168.123.132
 *    子网掩码：255.255.255.0
 *    （也写作 192.168.123.132/24）
 *    ip 地址 & 子网掩码：192.168.123.0，即为网络地址
 *    ip 地址 & ~子网掩码：0.0.0.132，即为主机地址
 */

// 交换两个整型变量的值
// int main()
// {
//     int a = 3, b = 5;
//     // 算法一（借助第三个变量）
//     // int tmp = a;
//     // a = b;
//     // b = tmp;
//
//     // 算法二（数学运算，可能溢出）
//     // a = a + b;
//     // b = a - b;
//     // a = a - b;
//
//     // 算法三（位运算）
//     a = a ^ b; // 第一次，记录下 a 与 b 的相同位和不同位
//     b = a ^ b;
//     a = a ^ b;
//     return 0;
// }

// 探究 >> 高位补 0 还是 1
// int main()
// {
//     // int a = -1;
//     unsigned a = -1;
//     for (int i = 0; i < sizeof(a) * 8; ++i)
//         printf("%#x\n", a >> i);
//     return 0;
// }
// >> 右移若干位，低位丢弃；
// 如果是无符号整型，高位补 0；
// 如果是有符号整型，高位补符号位；
// （有些编译器只补 0）
// << 左移若干位，高位丢弃，低位补 0

// 改变整数机器码的第 n 位
// int main()
// {
//     int a = 11; // 00001011
//     // 将 a 的机器码的第三位改为1
//     a |= (1 << 2);
//     printf("%d\n", a);
//
//     // 将 a 的机器码的第三位改为0
//     a &= ~(1 << 2);
//     printf("%d\n", a);
//     return 0;
// }

// 求一个整数的机器码有几个 1
// int main()
// {
//     int a = 128;
//     int count = 0;
//     // 法一
//     // int i;
//     // for (i = 0; i < sizeof(i) * 8; i++)
//     //     if ((a >> i) & 1)
//     //         count++;
//
//     // 法二
//     // unsigned tmp = (unsigned)a; // 因为 a 可能是负数
//     // while (tmp)
//     // {
//     //     if ((tmp & 1) == 1)
//     //         count++;
//     //     tmp >>= 1;
//     // }
//     // 对于负数，法一和法二一样快；对于正数，法二更快
//
//     // 法三(头皮发麻，震撼！！)
//     while (a)
//     {
//         a &= a - 1;
//         count++;
//     }
//     // 法三最快
//     printf("%d", count);
//     return 0;
// }

// % 只能用于整数运算。
// % 的结果可以是正数，也可以是负数，具体看编译器的处理。
// 一般情况：
// % 左边是正数则结果是正数，左边是负数则结果是负数

// 整数的二进制表示形式：原码 反码 补码
// 内存存储的是补码
// 正数：原码，反码和补码相同。
// 负数：~原码 == 反码(不改变符号位），反码 + 1 == 补码
